package com.KOL.model.character;

/*
 * PlayerCharacter.java
 * 
 * Copyright (c) 2008, Tyler Hoersch
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the University of Wisconsin Oshkosh nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS'' AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */

import com.KOL.model.DamageDealer;
import com.KOL.model.Enemy;
import java.awt.Point;
import java.io.Serializable;
import java.util.Arrays;
import java.util.StringTokenizer;
import java.util.Vector;

import com.KOL.model.Equipment;
import com.KOL.model.items.CharacterBackpack;
import com.KOL.model.items.EquipmentContainer;
import com.KOL.model.items.Item;
import com.KOL.model.Entity;
import com.KOL.model.TiledEntity;
import com.KOL.present.ChunkDisplay;
import com.util.ImagesLoader;

// TODO Character sheet seems to be the central hub for all things player related.  
// we should try to break away the character sheet to it's logical elements for
// maintainability.  Quests as a service locator, xp as a separate module, attributes
// into it's own manager.  This would help the CharacterSheet to be more focused
// and make it into a more maintainable implementation of a container as opposed
// to the full implementation.  Character sheets, in rpg's are always broken up
// into it's separate containers/divisions.
public class PlayerCharacter extends TiledEntity implements CharacterSheet, DamageDealer, Serializable {
    private static final long serialVersionUID = 1L;
    public final static int MOVE_MODIFIER = 2;

    private final int ATTRIBUTE_MODIFER = 2;
    private final double EXPIERENCE_MODIFER = 0.05;
    private final double ATTACK_MODIFER = 0.01;
    private final double CHANCE_MODIFER = 0.01;
    private final double LARGE_STAT_MODIFIER = 0.05;
    private final double SMALL_STAT_MODIFIER = 0.02;

    private Statistic health;
    private Statistic mana;

    private boolean isDead;
    private boolean inCombat;

    private String killedBy;
    
    private int strength;
    private int dexterity;
    private int toughness;
    private int intelligence;
    private int wisdom;
    private int defense;
    private int gold;
    private int silver;
    private int copper;
    private int level;
    private int xp;
    private int xpToLevel;

    private double meleeAttack;
    private double rangedAttack;
    private double magicIntuative;
    private double blockChance;
    private double dispelChance;
    private double critChance;
    private double dodgeChance;
    
    private String bio;
    private Sex sex;
    private Race race;

    private Point location;
    private Vector<Item> inventory;
    private Equipment[] equiped;
    private Classification role;
    private CharacterBackpack backpack;
    
    public PlayerCharacter(String hName,
                           String hBio,
                           Sex sex,
                           Classification hClass,
                           Race hRace,
                           int x,
                           int y,
                           int w,
                           int h,
                           ImagesLoader imsLd,
                           ChunkDisplay chunk) {

        super(x, y, w, h, imsLd, "still", chunk);

    	backpack = new CharacterBackpack();
    	
        level = 1;
        setXPToLevel(100);
        setStrength(10);
        setDexterity(10);
        setToughness(10);
        setIntelligence(10);
        setWisdom(10);
        defense = 100;        
        setCombatValues();
        setName(hName);
        setBio(hBio);
        this.sex = sex;
        this.race = hRace;
        setHeroClass(hClass);
        standStill();
        
        health = new Statistic(10);
        mana = new Statistic(10);
        mana.setValue(2);

        inCombat = false;
        isDead = false;
        killedBy = "";
    } 
    
    private void updateStats(Equipment equipment, boolean equip)
    {
        if(equip)
        {
            setStrength(getStrength() + equipment.getStrengthMod());
            setDexterity(getDexterity() + equipment.getDexterityMod());
            setToughness(getToughness() + equipment.getToughnessMod());
            setIntelligence(getIntelligence() + equipment.getIntelligenceMod());
            setWisdom(getWisdom() + equipment.getWisdomMod());
            defense = getDefense() + equipment.getDefenseMod();
        }
        else
        {
            setStrength(getStrength() - equipment.getStrengthMod());
            setDexterity(getDexterity() - equipment.getDexterityMod());
            setToughness(getToughness() - equipment.getToughnessMod());
            setIntelligence(getIntelligence() - equipment.getIntelligenceMod());
            setWisdom(getWisdom() - equipment.getWisdomMod());
            defense = getDefense() - equipment.getDefenseMod(); 
        }
        
        setCombatValues(); 
    }
    
    private void setCombatValues()
    {
        meleeAttack = (getStrength() * (getStrength() * LARGE_STAT_MODIFIER)) + (getDexterity() * (getDexterity() * SMALL_STAT_MODIFIER));
        magicIntuative = (getIntelligence() * (getIntelligence() * LARGE_STAT_MODIFIER)) + (getWisdom() * (getWisdom() * SMALL_STAT_MODIFIER));
        rangedAttack = (getDexterity() * (getDexterity() * LARGE_STAT_MODIFIER)) + (getStrength() * (getStrength() * SMALL_STAT_MODIFIER));
        critChance = (getDexterity() * (getDexterity() * LARGE_STAT_MODIFIER)) + (getStrength() * SMALL_STAT_MODIFIER);
        double dodge = (getToughness() * (getToughness() * LARGE_STAT_MODIFIER)) - (getDexterity()* (getDexterity() * SMALL_STAT_MODIFIER)) + (getStrength() * (getStrength() * SMALL_STAT_MODIFIER));
        blockChance = (getDexterity() * (getDexterity() * LARGE_STAT_MODIFIER)) - (getToughness()* (getToughness() * SMALL_STAT_MODIFIER)) + (getStrength() * (getStrength() * SMALL_STAT_MODIFIER));
        double dispel = (((getWisdom() + getIntelligence()) * LARGE_STAT_MODIFIER) * getWisdom()) - (getDexterity() * SMALL_STAT_MODIFIER) - (getStrength() * SMALL_STAT_MODIFIER);

        setDodgeChance(dodge);
        setDispelChance(dispel);
    }
    
    
    public void unequip(int index)
    {
        if(inventory.size() < inventory.capacity())
        {
            inventory.add(equiped[index]);
            updateStats(equiped[index],false);
            equiped[index] = null;
        }
    }
    public void equip(Equipment equipment)
    {
        if(equipment.getLevelRequirement() <= getLevel())
        {
            dropItem(equipment);
        
            if(equiped[equipment.getSlot()] == null)
                equiped[equipment.getSlot()] = equipment;
            else
            {
                pickUpItem(equiped[equipment.getSlot()]);
                equiped[equipment.getSlot()] = equipment;
            }
            
            updateStats(equipment, true);
        }
    }

    public void pickUpItem(Item item)
    {
        if(inventory.size() < inventory.capacity())
            inventory.add(item);
    }
    public int findItemIndex(Item item)
    {
        int index = -1;
        if(inventory.contains(item))
            index = inventory.indexOf(item);
        
        return index;
    }
    public Item findItemByIndex(int index)
    {
        return inventory.elementAt(index);
    }
    public void dropItem(int index)
    {
        if(inventory.elementAt(index) != null)
            inventory.remove(index);
    }
    public void dropItem(Item item)
    {
        if(inventory.contains(item))
            inventory.remove(item);   
    }

    @Override
    public EquipmentContainer getEquipmentContainer() {
            return backpack;
    }

    public boolean tryPickupItem() {
        String itemName;
        boolean success;

        if ((itemName = currentChunk.overItem(getTileLoc())) == null) {
            success = false;
        }else {
            currentChunk.removeItem(itemName);
            success = true;
        }

        return success;
    }
    
    public String getBio()
    {
        return bio;
    }
    public void setBio(String heroBio)
    {
        bio = heroBio;
    }
    
    public Sex getSex()
    {
        return sex;
    }
    
    public void setRace(Race hRace)
    {
        race = hRace;
    }
    public Race getRace()
    {
        return race;
    }
    public void setHeroClass(Classification hClass)
    {
        role = hClass;
        setRace(role.getRace());
    }
    public Classification getClassification()
    {
        return role;
    }
    
    public int getDefense()
    {
        return defense;
    }
    
    public double getCritChance() {
        return statRound(critChance);
    }
    public double getDodgeChance() {
        return statRound(dodgeChance);
    }
    public void setDodgeChance(double dodge)
    {
        dodgeChance = dodge;
    }
    public double getMeleeAttack()
    {
        return statRound(meleeAttack);
    }
    public double getRangedAttack()
    {
        return statRound(rangedAttack);
    }
    public double getMagicIntuative()
    {
        return statRound(magicIntuative);
    }
    public double getBlockChance() {
        return statRound(blockChance);
    }

    public double getDispelChance() {
        return statRound(dispelChance);
    }
    public void setDispelChance(double dispel)
    {
        dispelChance = dispel;
    }
    public int getStrength()
    {
        return strength;
    }
    public void setStrength(int str)
    {
        strength = str;
    }
    public int getDexterity()
    {
        return dexterity;
    }
    public void setDexterity(int dex)
    {
        dexterity = dex;
    }
    public int getToughness()
    {
        return toughness;
    }
    public void setToughness(int tough)
    {
        toughness = tough;
    }
    public int getIntelligence()
    {
        return intelligence;
    }
    public void setIntelligence(int intel)
    {
        intelligence = intel;
    }
    public int getWisdom()
    {
        return wisdom;
    }
    public void setWisdom(int wis)
    {
        wisdom = wis;
    }

    public String wasKilledBy() {
        return killedBy;
    }

    public Entity getEntity() {
        return this;
    }

    public void resurrect(Point p) {
        isDead = false;
        killedBy = "";

        health.setValue((int)(.05 * health.getMax()));
        mana.setValue((int)(.05 * mana.getMax()));

        setLocation(p);
    }

    public void death(DamageDealer source) {
        isDead = true;
        killedBy = source.getEntity().getName();
    }

    public boolean isDead() {
        return isDead;
    }

    public void takeDamage(int damage, DamageDealer source) {
        if(health.getValue() <= damage) {
            health.setValue(0);
            death(source);
        }else
            health.setValue(health.getValue() - damage);
    }

    public Statistic getHealth() {
    	return health;
    }
    
    public Statistic getMana() {
        return mana;
    }
   
    public Point getLocation()
    {
        return location;
    }
    public void setLocation(Point newLoc)
    {
        location = newLoc;
    }

    public void move(int quad) {
        Point newPt = tryMove(quad);

        if (newPt == null) {
            standStill();
        } else {
            setTileLoc(newPt);

            if (quad == NE)
                setImage(getClassImagePath() + "NE_Anim");
            else if (quad == SE)
                setImage(getClassImagePath() + "SE_Anim");
            else if (quad == SW)
                setImage(getClassImagePath() + "SW_Anim");
            else // quad == NW
                setImage(getClassImagePath() + "NW_Anim");

            getCurrentChunk().playerHasMoved(newPt, quad);
        }
    }

    public void standStill() {
        setImage(getClassImagePath() + "S_Still");
    }

    public void attacked() {
        // TODO implement
    }

    public String getClassImagePath() {
        String classPath = this.getClassification().getName();
        classPath = classPath.replaceAll(" ", "");
        classPath += "/";
        return classPath;
    }

    public void attack(Entity entity) {
        // TODO implement
    }

    public boolean inCombat() {
        return inCombat;
    }

    public void setCombatStatus(boolean cmbt) {
        inCombat = cmbt;
    }

    public void killedEnemy(Enemy enemy, double contributeFactor) {
        int levelDifference = enemy.getLevel() - this.getLevel();
        int expEarned = 0;

        if(levelDifference >= 0) {
            switch(levelDifference) {
                case 0:
                    expEarned = enemy.getExpWorth();
                case 1:
                    expEarned = (int)((enemy.getExpWorth() * 0.05) + enemy.getExpWorth());
                case 2:
                    expEarned = (int)((enemy.getExpWorth() * 0.1) + enemy.getExpWorth());
                case 3:
                    expEarned = (int)((enemy.getExpWorth() * 0.15) + enemy.getExpWorth());
                case 4:
                    expEarned = (int)((enemy.getExpWorth() * 0.2) + enemy.getExpWorth());
                case 5:
                    expEarned = (int)((enemy.getExpWorth() * 0.25) + enemy.getExpWorth());
                default:
                    expEarned = (int)(enemy.getExpWorth() * 0.1); // Assume cheated, or bugged kill
            }
        } else {
            switch(levelDifference) {
                case -1:
                    expEarned = (int)(enemy.getExpWorth() - (enemy.getExpWorth() * 0.05));
                case -2:
                    expEarned = (int)(enemy.getExpWorth() - (enemy.getExpWorth() * 0.1));
                case -3:
                    expEarned = (int)(enemy.getExpWorth() - (enemy.getExpWorth() * 0.15));
                default:
                    expEarned = 0;
            }
        }

        earnXP(expEarned);
    }

    public int getGold()
    {
        return gold;
    }
    public void addGold(int g)
    {
        gold += g;
    }
    public int getSilver()
    {
        return silver;
    }
    public void addSilver(int s)
    {
        silver += s;
        
        if(silver >= 100)
        {
            addGold(1);
            silver = silver - 100;
            addSilver(silver);
        }
    }
    public int getCopper()
    {
        return copper;
    }
    public void addCopper(int c)
    {
        copper += c;
        
        if(copper >= 100)
        {
            addSilver(1);
            copper = copper - 100;
            addCopper(copper);
        }
    }
    public int getLevel() {
        return level;
    }
    
    public void levelUp()
    {
        level++;
        
        setStrength(getStrength() + ATTRIBUTE_MODIFER);
        setDexterity(getDexterity()  + ATTRIBUTE_MODIFER);
        setToughness(getToughness() + ATTRIBUTE_MODIFER);
        setIntelligence(getIntelligence() + ATTRIBUTE_MODIFER);
        setWisdom(getWisdom() + ATTRIBUTE_MODIFER);
        
        meleeAttack = getMeleeAttack() + ATTACK_MODIFER;
        rangedAttack = getRangedAttack() + ATTACK_MODIFER;
        magicIntuative = getMagicIntuative() + ATTACK_MODIFER;
        
        blockChance = (getBlockChance() + CHANCE_MODIFER);
        setDispelChance(getDispelChance() + CHANCE_MODIFER);
        critChance = (getCritChance() + CHANCE_MODIFER);
        setDodgeChance(getDodgeChance() + CHANCE_MODIFER);
        
        setXPToLevel((int)(getXPToLevel() + EXPIERENCE_MODIFER));
        
        setCombatValues();
    }
    public int getXPToLevel()
    {
        return xpToLevel;
    }
    public void setXPToLevel(int exp)
    {
        xpToLevel = exp;
    }
    public int getXP()
    {
        return xp;
    }

    public void setXP(int iXp) {
        xp = iXp;
    }
    public void earnXP(int exp) {
        xp += exp;

        if(xp >= xpToLevel) {
            levelUp();
            xp = xp - xpToLevel;
        }
    }

    public double format(Double attribute)
    {
        int decimal_place = 2;
        java.math.BigDecimal bd = new java.math.BigDecimal(attribute);
        bd = bd.setScale(decimal_place,java.math.BigDecimal.ROUND_UP);
        return bd.doubleValue();
    }
    public String format(String incString)
    {
        String formatted = "";
        String bytes;
        int count = 0;
        StringTokenizer st = new StringTokenizer(incString);

        while(st.hasMoreTokens())
        {
            if(count < 32)
            {
                bytes = st.nextToken();
                count += bytes.length();
                formatted += bytes + " ";
            }
            else
            {
                count = 0;
                bytes = st.nextToken();
                count += bytes.length();
                formatted += "\n" + bytes + " ";
            }
        }

        return formatted;
    }

    double statRound(double d) {
        java.text.DecimalFormat twoDForm = new java.text.DecimalFormat("#.##");
		return Double.valueOf(twoDForm.format(d));
    }
    
    @Override
    public boolean equals(Object obj) {
    	if (obj instanceof CharacterSheet) {
    		CharacterSheet sheet = (CharacterSheet) obj;
    		return sheet.toString().equals(toString());
    	}
    	return false;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 47 * hash + this.strength;
        hash = 47 * hash + this.dexterity;
        hash = 47 * hash + this.toughness;
        hash = 47 * hash + this.intelligence;
        hash = 47 * hash + this.wisdom;
        hash = 47 * hash + this.defense;
        hash = 47 * hash + this.gold;
        hash = 47 * hash + this.silver;
        hash = 47 * hash + this.copper;
        hash = 47 * hash + this.level;
        hash = 47 * hash + this.xp;
        hash = 47 * hash + this.xpToLevel;
        hash = 47 * hash + (int) (Double.doubleToLongBits(this.meleeAttack) ^ (Double.doubleToLongBits(this.meleeAttack) >>> 32));
        hash = 47 * hash + (int) (Double.doubleToLongBits(this.rangedAttack) ^ (Double.doubleToLongBits(this.rangedAttack) >>> 32));
        hash = 47 * hash + (int) (Double.doubleToLongBits(this.magicIntuative) ^ (Double.doubleToLongBits(this.magicIntuative) >>> 32));
        hash = 47 * hash + (int) (Double.doubleToLongBits(this.blockChance) ^ (Double.doubleToLongBits(this.blockChance) >>> 32));
        hash = 47 * hash + (int) (Double.doubleToLongBits(this.dispelChance) ^ (Double.doubleToLongBits(this.dispelChance) >>> 32));
        hash = 47 * hash + (int) (Double.doubleToLongBits(this.critChance) ^ (Double.doubleToLongBits(this.critChance) >>> 32));
        hash = 47 * hash + (int) (Double.doubleToLongBits(this.dodgeChance) ^ (Double.doubleToLongBits(this.dodgeChance) >>> 32));
        hash = 47 * hash + (this.bio != null ? this.bio.hashCode() : 0);
        hash = 47 * hash + (this.sex != null ? this.sex.hashCode() : 0);
        hash = 47 * hash + (this.race != null ? this.race.hashCode() : 0);
        hash = 47 * hash + (this.location != null ? this.location.hashCode() : 0);
        hash = 47 * hash + (this.inventory != null ? this.inventory.hashCode() : 0);
        hash = 47 * hash + Arrays.deepHashCode(this.equiped);
        hash = 47 * hash + (this.role != null ? this.role.hashCode() : 0);
        hash = 47 * hash + (this.backpack != null ? this.backpack.hashCode() : 0);
        return hash;
    }

    @Override
    public String toString()
    {
        StringBuilder buffer = new StringBuilder();
        buffer.append(getName()).append("\n");
        buffer.append("                " + "\n");
        buffer.append("Level: ").append(getLevel()).append("\n");
        buffer.append(getRace()).append("\n");
        buffer.append(getSex()).append("\n");
        buffer.append(getClassification()).append( "\n");
        buffer.append("Bio: ").append(format(getBio())).append("\n\n");
        buffer.append("HP: ").append(getHealth().getValue()).append("/").append(getHealth().getMax()).append("\n");
        buffer.append("MP: ").append(getMana().getMax()).append("/").append(getMana().getValue()).append("\n\n");
        buffer.append("Strength: ").append(getStrength()).append("\n");
        buffer.append("Dexterity: ").append(getDexterity()).append("\n");
        buffer.append("Intelligence: ").append(getIntelligence()).append("\n");
        buffer.append("Toughness").append(getToughness()).append("\n");
        buffer.append("Wisdom: ").append(getWisdom()).append("\n");
        buffer.append("Defense: ").append(getDefense()).append("\n");
        buffer.append("Block: ").append(format(getBlockChance())).append("%\n");
        buffer.append("Critical: ").append(format(getCritChance())).append("%\n");
        buffer.append("Dispell: ").append(format(getDispelChance())).append("%\n");
        buffer.append("Dodge: ").append(format(getDodgeChance())).append("%\n\n");
        buffer.append("Magic Intuative: ").append(getMagicIntuative()).append("\n");
        buffer.append("Melee Attack: ").append(getMeleeAttack()).append("\n");
        buffer.append("Ranged Attack: ").append(getRangedAttack()).append("\n\n");
        buffer.append("Experience: ").append(getXP()).append("/").append(getXPToLevel()).append("\n");
        buffer.append("Located: ").append(getLocation()).append("\n");
        buffer.append("Gold: ").append(getGold()).append("\n");
        buffer.append("Silver: ").append(getSilver()).append("\n");
        buffer.append("Copper: ").append(getCopper()).append("\n");

        return buffer.toString();
    }
}
